local sim_objects_registry = nil local props_ini = ini_file("misc\\simulation_objects_props.ltx") config = ini_file_ex("ai_tweaks\\simulation_objects.ltx") local ALLOW_NPC_LEVEL_TRANSITION = true -- setup default squad behaviors local default_squad_behaviour = { ["actor"] = 0, ["all"] = 1, ["base"] = 1, ["lair"] = 0, ["resource"] = 2, ["squad"] = 0, ["surge"] = 3, ["territory"] = 1 } local default_monster_behaviour = { ["actor"] = 0, ["base"] = 0, ["lair"] = 1, ["monster"] = 1, ["resource"] = 0, ["squad"] = 0, ["surge"] = 0, ["territory"] = 1 } available_by_id = {} object_registry = {} object_registry_size = 0 base_smarts = {} -- this can cause the error: -- FATAL ERROR -- [error]Expression : -- [error]Function : invalid_parameter_handler -- [error]File : xrDebugNew.cpp -- [error]Line : 1028 -- [error]Description : invalid parameter -- because sometimes it runs when it shouldnt, so i change it to a function called when the value is needed in _g -- function actor_on_first_update() -- store unchangable info in _g, reuse rather than recall -- if not (IsTestMode() or level.name() == "fake_start") then -- _G.AC_LVL_ID = game_graph():vertex(alife():actor().m_game_vertex_id):level_id() -- end -- end function on_game_start() local index = 0 local function main_loop() index = index + 1 if (index > object_registry_size) then index = 0 end local se_obj = object_registry[index] if not (se_obj) then index = 0 return false end if (se_obj.sim_avail) then if (xr_logic.pick_section_from_condlist(nil, se_obj, se_obj.sim_avail) == "true") then available_by_id[se_obj.id] = true else available_by_id[se_obj.id] = false end else -- default true if no condlist available_by_id[se_obj.id] = true end return false end AddUniqueCall(main_loop) -- Why waste CPU on main thread per object update, we use PH Commander thread here. -- Load base smarts local ini_base_smrt = ini_file("misc\\smart_profiles.ltx") local n = ini_base_smrt:line_count("populated_smarts") for i=0,n-1 do local result, id, value = ini_base_smrt:r_line_ex("populated_smarts",i,"","") if base_smarts[id] == nil then base_smarts[id] = (value == "true") and true or false end end printf("# Loading ini: misc\\smart_profiles.ltx") end function register(se_obj) object_registry_size = object_registry_size + 1 object_registry[object_registry_size] = se_obj get_props(se_obj) if (xr_logic.pick_section_from_condlist(nil, se_obj, se_obj.sim_avail) == "true") then available_by_id[se_obj.id] = true else available_by_id[se_obj.id] = false end end function unregister(se_obj) for i=object_registry_size,1,-1 do if (object_registry[i].id == se_obj.id) then table.remove(object_registry,i) object_registry_size = object_registry_size - 1 return end end end function get_props(se_obj) local cls = se_obj:clsid() if (cls == clsid.online_offline_group_s) then return -- intentionally ignore squad targeting squads in coc end se_obj.props = {} local props_section if (cls == clsid.online_offline_group_s) then if (props_ini:section_exist(se_obj:section_name())) then props_section = se_obj:section_name() else props_section = "default_squad" end elseif (cls == clsid.script_actor) then props_section = "actor" else if (props_ini:section_exist(se_obj:name())) then props_section = se_obj:name() else props_section = "default" end end if not (props_ini:section_exist(props_section)) then printf("invalid section for props_ini %s",props_section) return end local n = props_ini:line_count(props_section) for j=0,n-1 do local result, prop_name, prop_condlist = props_ini:r_line_ex(props_section,j,"","") if prop_name == "sim_avail" then se_obj.sim_avail = xr_logic.parse_condlist(nil, "simulation_object", "sim_avail", prop_condlist) else se_obj.props[prop_name] = tonumber(prop_condlist) end end end function get_server_entity(id) for i=1,object_registry_size do local se_obj = object_registry[i] if (se_obj.id == id) then return se_obj end end end --*********************************************************************************************** --* SIMULATION_LOCAL_FUNCTIONS * --*********************************************************************************************** function sim_dist_to(obj1 , obj2) local pos1,lv1,gv1 = obj1:get_location() local pos2,lv2,gv2 = obj2:get_location() return utils_obj.graph_distance(gv1, gv2) end local function evaluate_prior_by_dist(target, squad) local dist = sim_dist_to(target, squad) if dist < 1 then dist = 1 end return 1 + 1/dist end function is_on_the_actor_level(se_obj) if (not se_obj) then return false end if get_player_level_id() then return (game_graph():vertex(se_obj.m_game_vertex_id):level_id() == get_player_level_id()) end return is_on_the_same_level(alife():actor(), se_obj) end function is_on_the_same_level(se_obj_1, se_obj_2) if not (se_obj_1 and se_obj_2) then return false end local gg = game_graph() return (gg:vertex(se_obj_1.m_game_vertex_id):level_id() == gg:vertex(se_obj_2.m_game_vertex_id):level_id()) end function is_on_the_linked_level(se_obj_1, se_obj_2) if not (se_obj_1 and se_obj_2) then return false end local sim = alife() local gg = game_graph() local obj1_level = sim:level_name(gg:vertex(se_obj_1.m_game_vertex_id):level_id()) local obj2_level = sim:level_name(gg:vertex(se_obj_2.m_game_vertex_id):level_id()) return (string.find(config:r_value(obj1_level,"target_maps",0,""),obj2_level) and true or false) end function is_on_the_nearby_level(se_obj_1, se_obj_2) -- linked and linked to linked level xddd if not (se_obj_1 and se_obj_2) then return false end local sim = alife() local gg = game_graph() local obj1_level = sim:level_name(gg:vertex(se_obj_1.m_game_vertex_id):level_id()) local obj2_level = sim:level_name(gg:vertex(se_obj_2.m_game_vertex_id):level_id()) if not (obj1_level and obj2_level) then return false end if (obj1_level == obj2_level) then return false end if string.find(config:r_value(obj1_level,"target_maps",0,""),obj2_level) then return true end local linked_levels = parse_list(config,obj1_level,"target_maps") for i=1,#linked_levels do if string.find(config:r_value(linked_levels[i],"target_maps",0,""),obj2_level) then return true end end return false end function evaluate_prior(target, squad) local sim = alife() local prior = 5 if (target:clsid() == clsid.script_actor) then if not (is_on_the_same_level(target, squad)) then return 0 end if not (target:target_precondition(squad)) then return 0 end elseif (target:clsid() == clsid.online_offline_group_s) then if (squad.id == target.id) then return 0 end if (not is_on_the_same_level(sim:actor(), squad)) then return 0 end -- If target squad is in safe zone, ignore them. if (xr_combat_ignore.safe_zone_npcs[squad.id]) then return 0 end if (xr_combat_ignore.safe_zone_npcs[target.id]) then return 0 end -- Prevent cluster-fucks local target_of_my_target = target.assigned_target_id and sim:object(target.assigned_target_id) if (target_of_my_target and target_of_my_target:clsid() == clsid.online_offline_group_s) then return 0 end if not (target:target_precondition(squad)) then return 0 end elseif (target:clsid() == clsid.smart_terrain) then if (target.disabled) then return 0 end if (SIMBOARD.smarts[target.id] and SIMBOARD.smarts[target.id].population >= target.max_population) then return 0 end if (SIMBOARD.smarts[target.id] == nil or SIMBOARD.smarts[target.id].squads == nil) then return 0 end -- Level travel if not (is_on_the_same_level(target, squad)) then if (is_squad_monster[squad.player_id]) then return 0 end if not (ALLOW_NPC_LEVEL_TRANSITION) then return 0 end end end -- priorize target based on target props local target_koeff = target.props[squad.player_id] --if (not target.props) then --callstack() --end if (target_koeff ~= nil) then prior = prior + (1*target_koeff) end if (is_squad_monster[squad.player_id]) then for k,v in pairs (default_monster_behaviour) do target_koeff = target.props[k] or 0 prior = prior + (v*target_koeff) end else for k,v in pairs (default_squad_behaviour) do target_koeff = target.props[k] or 0 prior = prior + (v*target_koeff) end end return prior*evaluate_prior_by_dist(target, squad) end